{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 数据处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 安装类库\n",
    "# !mkdir /home/aistudio/external-libraries\n",
    "# !pip install imgaug -t /home/aistudio/external-libraries\n",
    "import sys\n",
    "sys.path.append('/home/aistudio/external-libraries')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "image shape: (32, 32, 3)\n",
      "label value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "import matplotlib.pyplot as plt\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 读取数据\n",
    "reader = paddle.batch(\n",
    "    paddle.dataset.cifar.train10(),\n",
    "    batch_size=8) # 数据集读取器\n",
    "data = next(reader()) # 读取数据\n",
    "index = 4 # 批次索引\n",
    "\n",
    "# 读取图像\n",
    "image = np.array([x[0] for x in data]).astype(np.float32) # 读取图像数据，数据类型为float32\n",
    "image = image * 255 # 从[0,1]转换到[0,255]\n",
    "image = image[index].reshape((3, 32, 32)).transpose((1, 2, 0)).astype(np.uint8) # 数据格式从CHW转换为HWC，数据类型转换为uint8\n",
    "print('image shape:', image.shape)\n",
    "\n",
    "# 图像增强\n",
    "# sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "# seq = iaa.Sequential([\n",
    "#     sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "#     iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "# image = seq(image=image)\n",
    "\n",
    "# 读取标签\n",
    "label = np.array([x[1] for x in data]).astype(np.int64) # 读取标签数据，数据类型为int64\n",
    "vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "print('label value:', vlist[label[index]])\n",
    "\n",
    "# 显示图像\n",
    "image = Image.fromarray(image)   # 转换图像格式\n",
    "image.save('./work/out/img.png') # 保存读取图像\n",
    "plt.figure(figsize=(3, 3))       # 设置显示大小\n",
    "plt.imshow(image)                # 设置显示图像\n",
    "plt.show()                       # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n",
      "valid_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 训练数据增强\n",
    "def train_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 增强图像\n",
    "    sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "    seq = iaa.Sequential([\n",
    "        sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "        iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "    images = seq(images=images)\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 验证数据增强\n",
    "def valid_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 读取训练数据\n",
    "train_reader = paddle.batch(\n",
    "    paddle.reader.shuffle(paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "train_data = next(train_reader()) # 读取训练数据\n",
    "\n",
    "train_image = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取训练图像\n",
    "train_image = train_augment(train_image)                                                       # 训练图像增强\n",
    "train_label = np.array([x[1] for x in train_data]).reshape((-1, 1)).astype(np.int64)           # 读取训练标签\n",
    "print('train_data: image shape {}, label shape:{}'.format(train_image.shape, train_label.shape))\n",
    "\n",
    "# 读取验证数据\n",
    "valid_reader = paddle.batch(\n",
    "    paddle.dataset.cifar.test10(),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "valid_data = next(valid_reader()) # 读取验证数据\n",
    "\n",
    "valid_image = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取验证图像\n",
    "valid_image = valid_augment(valid_image)                                                       # 验证图像增强\n",
    "valid_label = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64)           # 读取验证标签\n",
    "print('valid_data: image shape {}, label shape:{}'.format(valid_image.shape, valid_label.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型设计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear, BatchNorm\n",
    "import math\n",
    "\n",
    "# 模组结构：输入维度，输出维度，滑动步长，基础长度\n",
    "group_arch = [(16, 16, 1, 3), (16, 32, 2, 3), (32, 64, 2, 3)]\n",
    "group_dim  = 64 # 模组输出维度\n",
    "class_dim  = 10 # 类别数量维度\n",
    "\n",
    "# 卷积单元\n",
    "class ConvUnit(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, filter_size=3, stride=1, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化卷积单元，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim      - 输入维度\n",
    "            out_dim     - 输出维度\n",
    "            filter_size - 卷积大小\n",
    "            stride      - 滑动步长\n",
    "            act         - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ConvUnit, self).__init__()\n",
    "        \n",
    "        # 添加卷积\n",
    "        self.conv = Conv2D(\n",
    "            num_channels=in_dim,\n",
    "            num_filters=out_dim,\n",
    "            filter_size=filter_size,\n",
    "            stride=stride,\n",
    "            padding=(filter_size-1)//2,                       # 输出特征图大小不变\n",
    "            param_attr=fluid.initializer.MSRA(uniform=False), # 使用MARA 初始权重\n",
    "            bias_attr=False,                                  # 卷积输出没有偏置项\n",
    "            act=None)\n",
    "        \n",
    "        # 添加正则\n",
    "        self.norm = BatchNorm(\n",
    "            num_channels=out_dim,\n",
    "            param_attr=fluid.initializer.Constant(1.0), # 使用常量初始化权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),  # 使用常量初始化偏置\n",
    "            act=act)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征进行卷积和正则\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 进行卷积\n",
    "        x = self.conv(x)\n",
    "        \n",
    "        # 进行正则\n",
    "        x = self.norm(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "# 分割结构\n",
    "class HSBlock(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, splits=5, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始HS-Block结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长，1保持不变，2下采样\n",
    "            splits  - 分割次数\n",
    "            act     - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(HSBlock, self).__init__()\n",
    "        \n",
    "        # 计算通道\n",
    "        channel0 = out_dim // splits\n",
    "        channel1 = channel0 * 2\n",
    "        channel2 = channel0 * splits\n",
    "        \n",
    "        # 特征平分\n",
    "        self.conv1 = ConvUnit(in_dim=in_dim, out_dim=channel2, filter_size=1, stride=1, act=act)\n",
    "        \n",
    "        # 特征升维\n",
    "        self.conv2 = ConvUnit(in_dim=channel0, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        \n",
    "        # 重复合并\n",
    "        self.conv3 = ConvUnit(in_dim=channel1, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        self.conv4 = ConvUnit(in_dim=channel1, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        self.conv5 = ConvUnit(in_dim=channel1, out_dim=channel1, filter_size=3, stride=1, act=act)\n",
    "        \n",
    "        # 合并特征\n",
    "        self.conv6 = ConvUnit(in_dim=channel2 + channel0, out_dim=out_dim, filter_size=1, stride=1, act=act)\n",
    "            \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 特征平分\n",
    "        x = self.conv1(x)\n",
    "        x0, x1, x2, x3, x4 = fluid.layers.split(input=x, num_or_sections=5, dim=1)\n",
    "        \n",
    "        # 特征升维\n",
    "        x1 = self.conv2(x1)\n",
    "        x1_0, x1_1 = fluid.layers.split(input=x1, num_or_sections=2, dim=1)\n",
    "        \n",
    "        # 重复合并\n",
    "        x2 = fluid.layers.concat(input=[x2, x1_1], axis=1)\n",
    "        x2 = self.conv3(x2)\n",
    "        x2_0, x2_1 = fluid.layers.split(input=x2, num_or_sections=2, dim=1)\n",
    "        \n",
    "        x3 = fluid.layers.concat(input=[x3, x2_1], axis=1)\n",
    "        x3 = self.conv4(x3)\n",
    "        x3_0, x3_1 = fluid.layers.split(input=x3, num_or_sections=2, dim=1)\n",
    "        \n",
    "        x4 = fluid.layers.concat(input=[x4, x3_1], axis=1)\n",
    "        x4 = self.conv5(x4)\n",
    "        \n",
    "        # 合并特征\n",
    "        x = fluid.layers.concat(input=[x0, x1_0, x2_0, x3_0, x4], axis=1)\n",
    "        x = self.conv6(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "# 基础结构\n",
    "class ResBasic(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, is_pass=True):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化基础结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            is_pass - 是否直连\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBasic, self).__init__()\n",
    "        \n",
    "        # 是否直连标识\n",
    "        self.is_pass = is_pass\n",
    "        \n",
    "        # 添加投影路径\n",
    "        self.proj = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=1, stride=stride, act=None)\n",
    "        \n",
    "        # 添加卷积路径\n",
    "        self.con1 = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=3, stride=stride, act='relu')\n",
    "        self.con2 = HSBlock(in_dim=out_dim, out_dim=out_dim, stride=1, splits=5, act='relu')\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "            y - 输出特征\n",
    "        \"\"\"\n",
    "        # 直连路径\n",
    "        if self.is_pass: # 是否直连\n",
    "            x_pass = x\n",
    "        else:            # 否则投影\n",
    "            x_pass = self.proj(x)\n",
    "        \n",
    "        # 卷积路径\n",
    "        x_con1 = self.con1(x)\n",
    "        x_con2 = self.con2(x_con1)\n",
    "        \n",
    "        # 输出特征\n",
    "        x = fluid.layers.elementwise_add(x=x_pass, y=x_con1, act='relu') # 直连路径与卷积路径进行特征相加\n",
    "        \n",
    "        return x\n",
    "    \n",
    "# 模块结构\n",
    "class ResBlock(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, basics=1):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模块结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            basics  - 基础长度\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBlock, self).__init__()\n",
    "        \n",
    "        # 添加模块列表\n",
    "        self.block_list = [] # 模块列表\n",
    "        for i in range(basics):\n",
    "            block_item = self.add_sublayer( # 构造模块项目\n",
    "                'block_' + str(i),\n",
    "                ResBasic(\n",
    "                    in_dim=(in_dim if i==0 else out_dim), # 每组模块项目除第一块外，输入维度=输出维度\n",
    "                    out_dim=out_dim,\n",
    "                    stride=(stride if i==0 else 1), # 每组模块项目除第一块外，stride=1\n",
    "                    is_pass=(False if i==0 else True))) # 每组模块项目除第一块外，is_pass=True\n",
    "            self.block_list.append(block_item) # 添加模块项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for block_item in self.block_list:\n",
    "            x = block_item(x) # 提取模块特征\n",
    "            \n",
    "        return x\n",
    "\n",
    "# 模组结构\n",
    "class ResGroup(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模组结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResGroup, self).__init__()\n",
    "        \n",
    "        # 添加模组列表\n",
    "        self.group_list = [] # 模组列表\n",
    "        for i, block_arch in enumerate(group_arch):\n",
    "            group_item = self.add_sublayer( # 构造模组项目\n",
    "                'group_' + str(i),\n",
    "                ResBlock(\n",
    "                    in_dim=block_arch[0],\n",
    "                    out_dim=block_arch[1],\n",
    "                    stride=block_arch[2],\n",
    "                    basics=block_arch[3]))\n",
    "            self.group_list.append(group_item) # 添加模组项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for group_item in self.group_list:\n",
    "            x = group_item(x) # 提取模组特征\n",
    "            \n",
    "        return x\n",
    "        \n",
    "# 残差网络\n",
    "class ResNet(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化残差网络，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResNet, self).__init__()\n",
    "        \n",
    "        # 添加初始化层\n",
    "        self.conv = ConvUnit(in_dim=3, out_dim=16, filter_size=3, stride=1, act='relu')\n",
    "        \n",
    "        # 添加模组结构\n",
    "        self.backbone = ResGroup() # 输出：N*C*H*W\n",
    "        \n",
    "        # 添加全连接层\n",
    "        self.pool = Pool2D(global_pooling=True, pool_type='avg') # 输出：N*C*1*1\n",
    "        \n",
    "        stdv = 1.0/(math.sqrt(group_dim)*1.0)                    # 设置均匀分布权重方差\n",
    "        self.fc = Linear(                                        # 输出：=N*10\n",
    "            input_dim=group_dim,\n",
    "            output_dim=class_dim,\n",
    "            param_attr=fluid.initializer.Uniform(-stdv, stdv),   # 使用均匀分布初始权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),           # 使用常量数值初始偏置\n",
    "            act='softmax')\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入图像进行分类\n",
    "        输入:\n",
    "            x - 输入图像\n",
    "        输出:\n",
    "            x - 预测结果\n",
    "        \"\"\"\n",
    "        # 提取特征\n",
    "        x = self.conv(x)\n",
    "        x = self.backbone(x)\n",
    "        \n",
    "        # 进行预测\n",
    "        x = self.pool(x)\n",
    "        x = fluid.layers.reshape(x, [x.shape[0], -1])\n",
    "        x = self.fc(x)\n",
    "        \n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tatol param: 248840\n",
      "infer shape: [1, 10]\n"
     ]
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.base import to_variable\n",
    "import numpy as np\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 输入数据\n",
    "    x = np.random.randn(1, 3, 32, 32).astype(np.float32)\n",
    "    x = to_variable(x)\n",
    "    \n",
    "    # 进行预测\n",
    "    backbone = ResNet() # 设置网络\n",
    "    \n",
    "    infer = backbone(x) # 进行预测\n",
    "    \n",
    "    # 显示结果\n",
    "    parameters = 0\n",
    "    for p in backbone.parameters():\n",
    "        parameters += np.prod(p.shape) # 统计参数\n",
    "    \n",
    "    print('tatol param:', parameters)\n",
    "    print('infer shape:', infer.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "complete - train time: 8825s, best epoch: 284, best loss: 0.325477, best accuracy: 90.29%\r"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "from paddle.utils.plot import Ploter\n",
    "import numpy as np\n",
    "import time\n",
    "import math\n",
    "import os\n",
    "\n",
    "epoch_num = 300   # 训练周期，取值一般为[1,300]\n",
    "train_batch = 128 # 训练批次，取值一般为[1,256]\n",
    "valid_batch = 128 # 验证批次，取值一般为[1,256]\n",
    "displays = 100    # 显示迭代\n",
    "\n",
    "start_lr = 0.00001                         # 开始学习率，取值一般为[1e-8,5e-1]\n",
    "based_lr = 0.1                             # 基础学习率，取值一般为[1e-8,5e-1]\n",
    "epoch_iters = math.ceil(50000/train_batch) # 每轮迭代数\n",
    "warmup_iter = 10 * epoch_iters             # 预热迭代数，取值一般为[1,10]\n",
    "\n",
    "momentum = 0.9     # 优化器动量\n",
    "l2_decay = 0.00005 # 正则化系数，取值一般为[1e-5,5e-4]\n",
    "epsilon = 0.05     # 标签平滑率，取值一般为[1e-2,1e-1]\n",
    "\n",
    "checkpoint = False                   # 断点标识\n",
    "model_path = './work/out/hs-resnet'  # 模型路径\n",
    "result_txt = './work/out/result.txt' # 结果文件\n",
    "class_num  = 10                      # 类别数量\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 准备数据\n",
    "    train_reader = paddle.batch(\n",
    "        reader=paddle.reader.shuffle(reader=paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "        batch_size=train_batch)\n",
    "    \n",
    "    valid_reader = paddle.batch(\n",
    "        reader=paddle.dataset.cifar.test10(),\n",
    "        batch_size=valid_batch)\n",
    "    \n",
    "    # 声明模型\n",
    "    model = ResNet()\n",
    "    \n",
    "    # 优化算法\n",
    "    consine_lr = fluid.layers.cosine_decay(based_lr, epoch_iters, epoch_num) # 余弦衰减策略\n",
    "    decayed_lr = fluid.layers.linear_lr_warmup(consine_lr, warmup_iter, start_lr, based_lr) # 线性预热策略\n",
    "    \n",
    "    optimizer = fluid.optimizer.Momentum(\n",
    "        learning_rate=decayed_lr,                           # 衰减学习策略\n",
    "        momentum=momentum,                                  # 优化动量系数\n",
    "        regularization=fluid.regularizer.L2Decay(l2_decay), # 正则衰减系数\n",
    "        parameter_list=model.parameters())\n",
    "    \n",
    "    # 加载断点\n",
    "    if checkpoint: # 是否加载断点文件\n",
    "        model_dict, optimizer_dict = fluid.load_dygraph(model_path) # 加载断点参数\n",
    "        model.set_dict(model_dict)                                  # 设置权重参数\n",
    "        optimizer.set_dict(optimizer_dict)                          # 设置优化参数\n",
    "    else:          # 否则删除结果文件\n",
    "        if os.path.exists(result_txt): # 如果存在结果文件\n",
    "            os.remove(result_txt)      # 那么删除结果文件\n",
    "    \n",
    "    # 初始训练\n",
    "    avg_train_loss = 0 # 平均训练损失\n",
    "    avg_valid_loss = 0 # 平均验证损失\n",
    "    avg_valid_accu = 0 # 平均验证精度\n",
    "    \n",
    "    iterator = 1                                # 迭代次数\n",
    "    train_prompt = \"Train loss\"                 # 训练标签\n",
    "    valid_prompt = \"Valid loss\"                 # 验证标签\n",
    "    ploter = Ploter(train_prompt, valid_prompt) # 训练图像\n",
    "    \n",
    "    best_epoch = 0           # 最好周期\n",
    "    best_accu = 0            # 最好精度\n",
    "    best_loss = 100.0        # 最好损失\n",
    "    train_time = time.time() # 训练时间\n",
    "    \n",
    "    # 开始训练\n",
    "    for epoch_id in range(epoch_num):\n",
    "        # 训练模型\n",
    "        model.train() # 设置训练\n",
    "        for batch_id, train_data in enumerate(train_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = train_augment(image_data)                                                        # 使用数据增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "\n",
    "            label_data = np.array([x[1] for x in train_data]).astype(np.int64)                        # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                             # 转换数据类型\n",
    "            label = fluid.layers.label_smooth(label=fluid.one_hot(label, class_num), epsilon=epsilon) # 使用标签平滑\n",
    "            label.stop_gradient = True                                                                # 停止梯度传播\n",
    "\n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label, soft_label=True)\n",
    "            train_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            # 反向传播\n",
    "            train_loss.backward()\n",
    "            optimizer.minimize(train_loss)\n",
    "            model.clear_gradients()\n",
    "            \n",
    "            # 显示结果\n",
    "            if iterator % displays == 0:\n",
    "                # 显示图像\n",
    "                avg_train_loss = train_loss.numpy()[0]                # 设置训练损失\n",
    "                ploter.append(train_prompt, iterator, avg_train_loss) # 添加训练图像\n",
    "                ploter.plot()                                         # 显示训练图像\n",
    "                \n",
    "                # 打印结果\n",
    "                print(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\".format(\n",
    "                    iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "                \n",
    "                # 写入文件\n",
    "                with open(result_txt, 'a') as file:\n",
    "                    file.write(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\\n\".format(\n",
    "                        iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "            \n",
    "            # 增加迭代\n",
    "            iterator += 1\n",
    "            \n",
    "        # 验证模型\n",
    "        valid_loss_list = [] # 验证损失列表\n",
    "        valid_accu_list = [] # 验证精度列表\n",
    "        \n",
    "        model.eval()   # 设置验证\n",
    "        for batch_id, valid_data in enumerate(valid_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = valid_augment(image_data)                                                        # 使用图像增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "            \n",
    "            label_data = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64) # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                       # 转换数据类型\n",
    "            label.stop_gradient = True                                                          # 停止梯度传播\n",
    "            \n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算精度\n",
    "            valid_accu = fluid.layers.accuracy(infer,label)\n",
    "            \n",
    "            valid_accu_list.append(valid_accu.numpy())\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label)\n",
    "            valid_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            valid_loss_list.append(valid_loss.numpy())\n",
    "        \n",
    "        # 设置结果\n",
    "        avg_valid_accu = np.mean(valid_accu_list)             # 设置验证精度\n",
    "        \n",
    "        avg_valid_loss = np.mean(valid_loss_list)             # 设置验证损失\n",
    "        ploter.append(valid_prompt, iterator, avg_valid_loss) # 添加训练图像\n",
    "        \n",
    "        # 保存模型\n",
    "        fluid.save_dygraph(model.state_dict(), model_path)     # 保存权重参数\n",
    "        fluid.save_dygraph(optimizer.state_dict(), model_path) # 保存优化参数\n",
    "        \n",
    "        if avg_valid_loss < best_loss:\n",
    "            fluid.save_dygraph(model.state_dict(), model_path + '-best') # 保存权重\n",
    "            \n",
    "            best_epoch = epoch_id + 1                                    # 更新迭代\n",
    "            best_accu = avg_valid_accu                                   # 更新精度\n",
    "            best_loss = avg_valid_loss                                   # 更新损失\n",
    "    \n",
    "    # 显示结果\n",
    "    train_time = time.time() - train_time # 设置训练时间\n",
    "    print('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}'.format(\n",
    "        train_time, best_epoch, best_loss, best_accu))\n",
    "    \n",
    "    # 写入文件\n",
    "    with open(result_txt, 'a') as file:\n",
    "        file.write('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}\\n'.format(\n",
    "            train_time, best_epoch, best_loss, best_accu))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "infer time: 0.026898s, infer value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "import time\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "image_path = './work/out/img.png' # 图片路径\n",
    "model_path = './work/out/hs-resnet-best' # 模型路径\n",
    "\n",
    "# 加载图像\n",
    "def load_image(image_path):\n",
    "    \"\"\"\n",
    "    功能:\n",
    "        读取图像并转换到输入格式\n",
    "    输入:\n",
    "        image_path - 输入图像路径\n",
    "    输出:\n",
    "        image - 输出图像\n",
    "    \"\"\"\n",
    "    # 读取图像\n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    \n",
    "    # 转换格式\n",
    "    image = image.resize((32, 32), Image.ANTIALIAS) # 调整图像大小\n",
    "    image = np.array(image, dtype=np.float32) # 转换数据格式，数据类型转换为float32\n",
    "\n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    image = (image/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    image = image.transpose((2, 0, 1)).astype(np.float32) # 数据格式从HWC转换为CHW，数据类型转换为float32\n",
    "    \n",
    "    # 增加维度\n",
    "    image = np.expand_dims(image, axis=0) # 增加数据维度\n",
    "    \n",
    "    return image\n",
    "\n",
    "# 预测图像\n",
    "with fluid.dygraph.guard():\n",
    "    # 读取图像\n",
    "    image = load_image(image_path)\n",
    "    image = fluid.dygraph.to_variable(image)\n",
    "    \n",
    "    # 加载模型\n",
    "    model = ResNet()                               # 加载模型\n",
    "    model_dict, _ = fluid.load_dygraph(model_path) # 加载权重\n",
    "    model.set_dict(model_dict)                     # 设置权重\n",
    "    model.eval()                                   # 设置验证\n",
    "    \n",
    "    # 前向传播\n",
    "    infer_time = time.time()              # 推断开始时间\n",
    "    infer = model(image)\n",
    "    infer_time = time.time() - infer_time # 推断结束时间\n",
    "    \n",
    "    # 显示结果\n",
    "    vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "    print('infer time: {:f}s, infer value: {}'.format(infer_time, vlist[np.argmax(infer.numpy())]) )\n",
    "    \n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    plt.figure(figsize=(3, 3))     # 设置显示大小\n",
    "    plt.imshow(image)              # 设置显示图像\n",
    "    plt.show()                     # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 1.8.4 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
